package com.nnnu.wsnackshop.filter;

import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nnnu.wsnackshop.exception.ObjectException;
import com.nnnu.wsnackshop.utils.JwtToken;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.*;

@Component
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {

    /**
     * 默认开启跨域设置（使用单体）
     * 微服务情况下，此属性设置为false
     */
    private boolean allowOrigin = true;

    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 允许匿名访问的路径前缀
     */
    private static final Set<String> ANONYMOUS_PATHS = new HashSet<>(Arrays.asList(
            "/api/auth/",
            "/api/rider/auth/",
            "/api/captcha/",
            "/swagger-ui/",
            "/v3/api-docs/",
            "/api/user/banners",
            "/api/user/categories/top",
            "/api/user/categories/list",
            "/api/user/categories/tree",
            "/api/user/products/popular",
            "/api/user/products/new",
            "/api/user/products/by-category/",
            "/api/user/products/search",
            "/api/user/products/page",
            "/api/user/products/tag/",
            "/api/user/product-tags/popular",
            "/api/ws/notifications",
            "/api/ws/sockjs/notifications/info",
            "/api/ws/sockjs/notifications/iframe.html",
            "/api/ws/sockjs/notifications/087/hvepdxut/htmlfile",
            "/api/user/stores"));

    // 无参构造函数，用于配置类中创建实例
    public JwtFilter() {
        this.allowOrigin = true;
    }

    public JwtFilter(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
        this.allowOrigin = true;
    }

    public JwtFilter(RedisTemplate<String, Object> redisTemplate, boolean allowOrigin) {
        this.redisTemplate = redisTemplate;
        this.allowOrigin = allowOrigin;
    }

    /**
     * 判断路径是否允许匿名访问
     */
    private boolean isAnonymousPath(String path) {
        // 精确匹配
        if (ANONYMOUS_PATHS.contains(path)) {
            return true;
        }

        // 前缀匹配
        for (String prefix : ANONYMOUS_PATHS) {
            if (path.startsWith(prefix)) {
                return true;
            }
        }

        // 特别处理商品详情页路径 /api/user/products/{id}
        return path.matches("/api/user/products/\\d+");
    }

    /**
     * 执行登录认证
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String path = httpServletRequest.getRequestURI();

        // 检查是否是OPTIONS请求
        if (RequestMethod.OPTIONS.name().equalsIgnoreCase(httpServletRequest.getMethod())) {
            return true; // 允许OPTIONS请求通过
        }

        // 检查是否是匿名路径
        if (isAnonymousPath(path)) {
            log.debug("匿名路径，允许访问: {}", path);
            return true;
        }

        // 非匿名路径，需要执行登录认证
        try {
            executeLogin(request, response);
            return true;
        } catch (ObjectException | AuthenticationException e) {
            log.error("认证失败：{}, 路径: {}", e.getMessage(), path);
            return false; // 拒绝访问，触发onAccessDenied
        } catch (AuthorizationException e) {
            throw e; // 重新抛出AuthorizationException由全局异常处理器处理
        } catch (Exception e) {
            log.error("发生未知异常：{}, 路径: {}", e.getMessage(), path);
            return false; // 拒绝访问，触发onAccessDenied
        }
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("Authorization");
        if (StrUtil.isEmpty(token)) {
            throw new AuthenticationException("未提供 Token，请先登录");
        }

        // 检查令牌是否在黑名单中，仅当RedisTemplate可用时
        if (redisTemplate != null && Boolean.TRUE.equals(redisTemplate.hasKey("token_blacklist:" + token))) {
            throw new AuthenticationException("令牌已失效，请重新登录");
        }

        JwtToken jwtToken = new JwtToken(token);
        // 提交给realm进行登入，如果错误他会抛出异常并被捕获
        getSubject(request, response).login(jwtToken);
        // 如果没有抛出异常则代表登入成功，返回true
        return true;
    }

    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        if (allowOrigin) {
            // 优先使用Origin头
            String origin = httpServletRequest.getHeader(HttpHeaders.ORIGIN);
            if (origin != null) {
                httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
            } else {
                // 回退到Referer头
                String referer = httpServletRequest.getHeader("Referer");
                if (referer != null) {
                    // 从Referer中提取域名
                    int end = referer.indexOf("/", 8); // 跳过https://
                    if (end != -1) {
                        String originFromReferer = referer.substring(0, end);
                        httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, originFromReferer);
                    } else {
                        httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
                    }
                } else {
                    // 否则允许所有源
                    httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
                }
            }

            // 允许客户端请求方法
            httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,POST,OPTIONS,PUT,DELETE");
            // 允许客户端提交的Header
            String requestHeaders = httpServletRequest.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
            if (StrUtil.isNotEmpty(requestHeaders)) {
                httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders);
            } else {
                // 默认允许常用头
                httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,
                        "Origin, X-Requested-With, Content-Type, Accept, Authorization");
            }
            // 允许客户端携带凭证信息(是否允许发送Cookie)
            httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
            // 预检请求缓存时间
            httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600");
        }

        // 处理OPTIONS请求
        if (RequestMethod.OPTIONS.name().equalsIgnoreCase(httpServletRequest.getMethod())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false; // 返回false阻止继续处理OPTIONS请求
        }

        return super.preHandle(request, response);
    }

    // Override onAccessDenied to prevent the browser login pop-up
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        String path = WebUtils.toHttp(request).getRequestURI();

        // Set status to 401 Unauthorized
        httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
        httpResponse.setContentType("application/json;charset=UTF-8");

        // Write custom JSON error response
        String errorMessage = "认证失败，请先登录。";

        try {
            httpResponse.getWriter().write(new ObjectMapper()
                    .writeValueAsString(Map.of(
                            "code", HttpStatus.UNAUTHORIZED.value(),
                            "message", errorMessage,
                            "path", path)));
        } catch (Exception e) {
            log.error("写入认证失败响应失败", e);
        }
        return false; // Indicates that the response has been handled
    }
}
